#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <google/protobuf/message.h>
#include <boost/filesystem.hpp>
#include <sys/sysinfo.h>

#include "../util/multi.pb.h"
#include "../util/hmac.h"
#include "../util/timeservice.h"
#include <openssl/hmac.h>

#define MULTICASTIP "172.16.0.1"
#define CLIENT_MULTICASTPORT 3200
#define BLOB_SIZE 1000

#define MAXBUFSIZE 65535

class udp_sender2a{
public:
	udp_sender2a(int port) 
	{
   unsigned char ttl = 3;
   unsigned char one = 1;

   // set content of struct saddr and imreq to zero
   memset(&saddr, 0, sizeof(struct sockaddr_in));
   memset(&iaddr, 0, sizeof(struct in_addr));

   // open a UDP socket
   sock = socket(PF_INET, SOCK_DGRAM, 0);

   if ( sock < 0 )
     perror("Error creating socket"), exit(0);

   saddr.sin_family = PF_INET;
   saddr.sin_port = htons(0); // Use the first free port
   saddr.sin_addr.s_addr = htonl(INADDR_ANY); // bind socket to any interface
   status = bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));

   if ( status < 0 )
     perror("Error binding socket to interface"), exit(0);

   iaddr.s_addr = INADDR_ANY; // use DEFAULT interface

   // Set the outgoing interface to DEFAULT
   setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &iaddr,
	      sizeof(struct in_addr));

   // Set multicast packet TTL to 3; default TTL is 1
   setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
	      sizeof(unsigned char));

   // send multicast traffic to myself too
   status = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP,
		       &one, sizeof(unsigned char));

   // set destination multicast address
   saddr.sin_family = PF_INET;
   saddr.sin_addr.s_addr = inet_addr("172.16.0.1");
   saddr.sin_port = htons(3200);

   // put some data in buffer
   strcpy(buffer, "Hello world\n");

   socklen = sizeof(struct sockaddr_in);
   // receive packet from socket
/*
   status = sendto(sock, buffer, strlen(buffer), 0,
		     (struct sockaddr *)&saddr, socklen);

*/


	}
	~udp_sender2a()
	{
   		// shutdown socket
   		shutdown(sock, 2);
   		// close socket
   		close(sock);

	} 

	void send(server::Block const& block) 
	{
		pthread_mutex_lock(&m_lock);
		std::string data;
		block.SerializeToString(&data);
		data.append(hmac(data));
		sendto(sock,data.c_str(),data.size(),0,
		       (struct sockaddr *)&saddr,socklen);
		usleep(10000);

		pthread_mutex_unlock(&m_lock);
	}
private:
	pthread_mutex_t m_lock;

   int sock, status, socklen;
   char buffer[MAXBUFSIZE];
   struct sockaddr_in saddr;
   struct in_addr iaddr;

};


class udp_sender {
public:
	udp_sender(int port) {
		m_sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);

		bzero(&m_servaddr,sizeof(m_servaddr));
		m_servaddr.sin_family = AF_INET;
		m_servaddr.sin_port=htons(port);
		m_servaddr.sin_addr.s_addr=inet_addr(MULTICASTIP);
	}
	void send(std::string data) {
		pthread_mutex_lock(&m_lock);
		sendto(m_sockfd,data.c_str(),data.size(),0,
		       (struct sockaddr *)&m_servaddr,sizeof(m_servaddr));

		pthread_mutex_unlock(&m_lock);
	}

	void send(server::Block const& block) {
		pthread_mutex_lock(&m_lock);
		std::string data;
		block.SerializeToString(&data);
		data.append(hmac(data));
		sendto(m_sockfd,data.c_str(),data.size(),0,
		       (struct sockaddr *)&m_servaddr,sizeof(m_servaddr));
		usleep(10000);

		pthread_mutex_unlock(&m_lock);
	}

private:
	int m_sockfd,m_n;
	struct sockaddr_in m_servaddr,m_cliaddr;
	pthread_mutex_t m_lock;
};

udp_sender sender(CLIENT_MULTICASTPORT);


void* heartbeatSendThread(void *arg) {
	while(1) {
//		std::cout << "send" << std::endl;
	 struct sysinfo sys_info;

	if(sysinfo(&sys_info) != 0)
		perror("sysinfo");

//	std::cout << "sys_info=" << sys_info.totalram << std::endl; 

		server::Block block;
		unsigned int t = static_cast<unsigned int>(time(NULL));
		block.mutable_heartbeat()->set_time(t);
		block.mutable_heartbeat()->set_totalram(sys_info.totalram);
		block.mutable_heartbeat()->set_freeram(sys_info.freeram);
		block.mutable_heartbeat()->set_load(sys_info.loads[0]);
		block.mutable_heartbeat()->set_uptime(sys_info.uptime);
		sender.send(block);
		sleep(1);
	}
	return NULL;
}


int main(int argc, char**argv) {

   /* Our process ID and Session ID */

                pid_t pid, sid;

                pid = fork();
                if (pid < 0)
                {
                        exit(EXIT_FAILURE);
                }

                if (pid > 0)
                {
                        exit(EXIT_SUCCESS);
                }

	TimeService timeservice;
	timeservice.client();

	srand (time(NULL));


		pthread_t heartbeatProcess;
		pthread_create(&(heartbeatProcess), NULL, &heartbeatSendThread, NULL);


		pthread_join(heartbeatProcess, NULL);


	return 0;
}
